#! /usr/bin/env python
# -*- coding: latin-1 -*-
## @package edi_core
#  Core module
#
#
# Copyright (C) 2014 Spartatek AB
#
# contact@spartatek.se
# http://spartatek.se
#
# EASYDONEIT CLI is free software: you can redistribute it and/or modify
# it under the terms of the GNU Genereric Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# EASYDONIT CLI is distributed in the hope that it will be usesul,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Genereral Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
# GNU licences http://www.gnu.org/licenses

import sys
import re
import time
import shutil
import os
import ConfigParser
from string import ascii_lowercase
from string import ascii_uppercase
from string import digits
import random
import stat
import getpass

from edi_common import *

## user_interface is initialized in start() and changes the behavior of some functions to allow a functional user interface design. The default is not used, start() is always called before using edi_core.
# Values: 'cli', 'web'
user_interface       = 'cli'

data_location        = ''
data_location_tasks  = ''
data_location_groups = ''
data_location_tree   = ''

## Available databases
databases            = []

## list selected databases
selected             = []
selected_path        = []
## default database where tasks are created
default_add_in       = ''

## add new tasks in 'bottom' of group or 'top' of group
add_top_or_bottom    = 'bottom'

## Autlink groups (array of tids)
autolink             = ''

## list of groups for edi ls -L, -La and -Lx (array of tids)
list_of_groups       = ''

## characters for task ids
ID_BASE              = sorted(list(digits)+list(ascii_lowercase)+list(ascii_uppercase)+['_',','])
ID_BASE_STRING       = ''.join(ID_BASE)
## Length of task id
ID_LENGTH            = 16
## Length of order id in groups
ORDER_ID_LENGTH      = 8
BASE                 = len(ID_BASE)

TASK_STATUS          = ['  Active',
                        '    Done',
                        ' Ongoing',
			' Pending',
                        'Inactive',
                        '    Void']
TASK_STATUS_ACTIVE   = 0
TASK_STATUS_DONE     = 1
TASK_STATUS_ONGOING  = 2
TASK_STATUS_PENDING  = 3
TASK_STATUS_INACTIVE = 4
TASK_STATUS_VOID     = 5

## Sort order for sort_task_attributes function, ongoing, active, pending, done, inactive, void
SORT_TASK_ORDER      = [2,0,3,1,4,5]

LIST_OPTIONS         = ['tids',
			'positions',
			'html']
list_option          = 'tids'

STATUS_FILTER_STATES = ['enable','disable']

## enables all status filters
status_filters       = [STATUS_FILTER_STATES[0] for i in range(len(TASK_STATUS))]
## status filter dictionary, initialized after loading ini file
status_filters_d     = {}

## colors
no_color             = (-1,-1,-1,255)
#status_fgColors      = [(0,0,0,255) for i in range(len(TASK_STATUS))]
status_fgColors      = [(0,0,0,255),(0,255,0,255),(255,128,0,255),(160,32,240,255),(192,192,192,255),(0,0,0,255)]
status_fgColors_d    = {}
## no background color by default
status_bgColors      = [no_color for i in range(len(TASK_STATUS))]
status_fgColors_d    = {}

## text editor in terminal - easydoneit.ini [settings] EDITOR
editor               = 'vi'

## Agenda generated by edi.in_cli
agenda               = []

## user name
user                 = ''

## user email
email                = ''

## stats for statistics function
stats                = {}

## total number of tasks for statistics function
stats_total          = 0

## creation dates of tasks in statistics, used to find out oldest task
stats_creation_dates = []

## timely state changes and creation dates
# Type: dict of dict of integers
# keys are dates
# each dates is a dictionary with STATS_OVERTIME_KEYS keys
# each key is the amount for states and creation
stats_overtime       = {}

## timely state changes and creation dates
STATS_OVERTIME_KEYS  = [TASK_STATUS[i] for i in range(len(TASK_STATUS))] + ['Creation']

## Group directory, cache result to speed up color functions
group_directory_file_list = []

## initializes path to data.
# creates directories<br>
# creates root group and task<br>
# @ingroup EDI_CORE
def init():
	# Set global variables
	global data_location_tasks
	global data_location_groups
	global data_location_tree
	global status_filters
	global status_filters_d
	global status_fgColors
	global status_fgColors_d
	global status_bgColors
	global status_bgColors_d

	status_filters_d     = dict(zip(TASK_STATUS,status_filters))
	status_fgColors_d    = dict(zip(TASK_STATUS,status_fgColors))
	status_bgColors_d    = dict(zip(TASK_STATUS,status_bgColors))

	data_location_tasks  = '%s/tasks'%data_location
	data_location_groups = '%s/groups'%data_location
	data_location_tree   = '%s/tree'%data_location

	if not os.path.exists(data_location_tasks):
		if not os.path.exists(data_location):
			os.mkdir(data_location)
		os.mkdir(data_location_tasks)
		os.mkdir(data_location_groups)
		os.mkdir(data_location_tree)
		os.mkdir('%s/root'%data_location_groups)

		# Create root description in tasks
		task_path = '%s/root'%data_location_tasks
		os.mkdir(task_path)
		# copy root description from script directory
		shutil.copy('%s/root.txt'%os.path.abspath(os.path.dirname(sys.argv[0])),'%s/description.txt'%task_path)

		# Create status
		f         = open('%s/status'%task_path,'w')
		f.write(TASK_STATUS[TASK_STATUS_VOID])
		f.close()

## log actions in database - data_location/log.txt
# @param[in] s string without user name and timestamp
# @ingroup EDI_CORE
def edi_log(s):
	global user
	global email
	f = open('%s/log.txt'%data_location,'a')
	f.write('%s - %s - %s\n'%(time.strftime("%Y-%m-%d %H:%M"), '%s <%s>'%(user,email),s.strip()))
	f.close()


## select location database
# @param[in] location database name
# @ingroup EDI_CORE
def select_database(location):
	global data_location
	global data_location_tasks
	global data_location_groups
	global data_location_tree

	z = dict(zip(selected, selected_path))
	data_location        = os.path.expanduser(z[location])
	data_location_tasks  = '%s/tasks'%data_location
	data_location_groups = '%s/groups'%data_location
	data_location_tree   = '%s/tree'%data_location
	if not os.path.exists(data_location):
		return '%s is unreachable.'%data_location
	else:
		return ''

## Mix foreground colors from task, groups and default colors.
# @return color array
# @param[in] tid task id
# @ingroup EDI_CORE
# Collect all defined colors in link groups.
# For each group, the group gets the first color defined in the tree<br>
# compute average color
def mix_fgcolors(tid):
	# mix colors
	# collect all defined colors in groups to root
	colors = []
	# root has no parent group, dont search parent group color
	if tid != 'root':
		# find all groups - link and parent group
		if is_linked(tid):
			groups = os.listdir('%s/groups/' % generate_task_path(tid))
		else:
			groups = [find_group_containing_task(tid)]

		# walk parent groups until a color or root is found
		while groups:
			status_color = 'search'
			if os.path.isfile('%s/fgColor'%generate_task_path(groups[0])):
				f       = open('%s/fgColor'%generate_task_path(groups[0]))
				c       = tuple([int(i) for i in f.readline().split(',')])
				f.close()
				# -1 means no_color, mix colors
				if c[0] != -1:
					colors.append(c)
					status_color = 'found color'
			if status_color == 'search':
				parent_group = find_group_containing_task(groups[0])
				if parent_group != 'root':
					groups.append(parent_group)
			del groups[0]

		# compute average color
		if colors:
			color = [0,0,0,0]
			for c in colors:
				for i in range(len(c)):
					color[i] += c[i]
			colorCount = len(colors)
			color      = tuple([i/colorCount for i in color])
	if not colors:
		# no defined colors, use default color for status
		task_status = get_status(tid)
		color       = status_fgColors_d[task_status]
	return color

## get color for task tid
# @return color array
# @param[in] tid task id
# @ingroup EDI_CORE
# when no color is set, mix colors from groups or use defaults
def get_forground_color(tid):
	color = no_color

	if os.path.isfile('%s/fgColor'%generate_task_path(tid)):
		f       = open('%s/fgColor'%generate_task_path(tid))
		color   = tuple([int(i) for i in f.readline().split(',')])
		f.close()
		# -1 means no_color, mix colors
		if color[0] == -1:
			color = mix_fgcolors(tid)
	else:
		color   = mix_fgcolors(tid)
	return color

## set color for task tid
# @return color array
# @param[in] tid task id
# @param[in] color_s color array
# @ingroup EDI_CORE
def set_forground_color(tid,color_s):
	f       = open('%s/fgColor'%generate_task_path(tid),'w')
	f.write(color_s)
	f.close()
	edi_log('set foreground color in %s to %s'%(tid,color_s))

## Mix background colors from task, groups and default colors.
# @return color array
# @param[in] tid task id
# @ingroup EDI_CORE
# Collect all defined colors in link groups.
# For each group, the group gets the first color defined in the tree<br>
# compute average color
def mix_bgcolors(tid):
	# mix colors
	# collect all defined colors in groups to root
	colors = []
	# root has no parent group, dont search parent group color
	if tid != 'root':
		# find all groups - link and parent group
		if is_linked(tid):
			groups = os.listdir('%s/groups/' % generate_task_path(tid))
		else:
			groups = [find_group_containing_task(tid)]

		# walk parent groups until a color or root is found
		while groups:
			status_color = 'search'
			if os.path.isfile('%s/bgColor'%generate_task_path(groups[0])):
				f       = open('%s/bgColor'%generate_task_path(groups[0]))
				c       = tuple([int(i) for i in f.readline().split(',')])
				f.close()
				# -1 means no_color, mix colors
				if c[0] != -1:
					colors.append(c)
					status_color = 'found color'
			if status_color == 'search':
				parent_group = find_group_containing_task(groups[0])
				if parent_group != 'root':
					groups.append(parent_group)
			del groups[0]

		# compute average color
		if colors:
			color = [0,0,0,0]
			for c in colors:
				for i in range(len(c)):
					color[i] += c[i]
			colorCount = len(colors)
			color      = tuple([i/colorCount for i in color])
	if not colors:
		# no defined colors, use default color for status
		task_status = get_status(tid)
		color       = status_bgColors_d[task_status]
	return color

## get color for task tid
# @return color array
# @param[in] tid task id
# @ingroup EDI_CORE
# when no color is set, mix colors from groups or use defaults
def get_background_color(tid):
	color = no_color

	if os.path.isfile('%s/bgColor'%generate_task_path(tid)):
		f       = open('%s/bgColor'%generate_task_path(tid))
		color   = tuple([int(i) for i in f.readline().split(',')])
		f.close()
		# -1 means no_color, mix colors
		if color[0] == -1:
			color = mix_bgcolors(tid)
	else:
		color   = mix_bgcolors(tid)
	return color

## set color for task tid
# @return color array
# @param[in] tid task id
# @param[in] color_s color array
# @ingroup EDI_CORE
def set_background_color(tid,color_s):
	f       = open('%s/bgColor'%generate_task_path(tid),'w')
	f.write(color_s)
	f.close()
	edi_log('set background color in %s to %s'%(tid,color_s))

## converts color to hex format
# @return color hex string
# @param[in] color color array
# @ingroup EDI_CORE
def color_to_hex(color):
	c = []
	for n in color:
		if n == -1:
			# -1 is no color, white
			c.append(255)
		else:
			c.append(n)
	color = tuple(c)
	return '%02x%02x%02x' % (color[0],color[1],color[2])

## filesystem path for task in database tasks
# @return path
# @param[in] tid task id
# @ingroup EDI_CORE
def generate_task_path(tid):
	return '%s/%s'%(data_location_tasks,tid)

## filesystem path for group in database groups
# @return path
# @param[in] tid task id
# @ingroup EDI_CORE
def generate_group_path(tid):
	return '%s/%s/'%(data_location_groups,tid)

## get status for tid
# @return status string
# @param[in] tid task id
# @ingroup EDI_CORE
def get_status(tid):
	# open status file
	try:
		f      = open('%s/status'%generate_task_path(tid))
		status = f.readline()
		f.close()
	except:
		print '%s is an invalid task.'%generate_task_path(tid)
		status = TASK_STATUS[TASK_STATUS_VOID]
	return status

## creates a string with task id, status and string parameter
# @return list of strings
# @param[in] tid task id
# @param[in] s task title string
# @ingroup EDI_CORE
# Displays a task depending on edi_core.list_option
def generate_task_string_with_tid(tid,s):
	status = get_status(tid)
	if list_option == 'tids':
		# %*s to dynamically adjust id length
		r             = '%*s - %s -   %s'%(ID_LENGTH,tid,status,s)
	if list_option == 'positions':
		if is_this_task_a_group(tid):
			# print tid for groups in the list
			r             = '%*s - %s -   %s'%(ID_LENGTH,tid,status,s)
		else:
			tid           = ' '
			r             = '%*s   %s -   %s'%(ID_LENGTH,tid,status,s)
	if list_option == 'md':
		# print title and description
		f             = open('%s/description.txt'%generate_task_path(tid))
		# remove title line
		description   = ''.join(f.readlines()[1:])
		f.close()

		tid           = ' '
		# add <br> for long titles
		r             = '\n---\n#%s<br>\n%s'%(s,description)
	if list_option == 'rst':
		# print title and description
		f             = open('%s/description.txt'%generate_task_path(tid))
		# remove title line
		description_l = f.readlines()[1:]
		f.close()

		# convert urls to link
		NOBRACKET = r'[^\]\[]*'
		BRK = ( r'\[('
			+ (NOBRACKET + r'(\[')*6
			+ (NOBRACKET+ r'\])*')*6
			+ NOBRACKET + r')\]' )
		NOIMG = r'(?<!\!)'
		LINK_RE = NOIMG + BRK + \
		r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
		# [text](url) or [text](<url>) or [text](url "title")

		compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE)

		for line_index,l in enumerate(description_l):
			if 'http' in l:
				try:
					match      = compiled_re.match(l)
					url        = match.group(9)
					text       = match.group(2)
					before_url = match.group(1)
					#title      = match.group(13)
					after_url  = match.group(14)

					l = '%s`%s<%s>`_%s'% (before_url, text, url, after_url)
				except:
					# not used, information
					link_status = 'WARNING: the link is not in format [title](url)'
			description_l[line_index] = l

		description   = ''.join(description_l)

		tid           = ' '
		# remove position and type (GROUP, LINK) from title
		title         = s[11:].lstrip()
		r             = '%s\n'%title + '='*len(title) + '\n%s\n\n'%description
	if list_option == 'html':
		#<tr bgcolor="#FEFFCC">
		#  <td class="subject">          <font color="#000000">t1</font></td>
		#  <td class="description">          <font color="#000000">&nbsp;</font></td>
		#</tr>
#:define read_description
		# convert < to &lt; and > &gt; to ignore html tags in title line
		s             = s.replace('<','&lt;').replace('>','&gt;')
		f             = open('%s/description.txt'%generate_task_path(tid))
		# remove title line, convert < to &lt; and > &gt; to ignore html tags
		description_l = ['%s<br>'%i.rstrip().replace('<','&lt;').replace('>','&gt;') for i in f.readlines()[1:]]
		f.close()

		# convert urls to link
		NOBRACKET = r'[^\]\[]*'
		BRK = ( r'\[('
			+ (NOBRACKET + r'(\[')*6
			+ (NOBRACKET+ r'\])*')*6
			+ NOBRACKET + r')\]' )
		NOIMG = r'(?<!\!)'
		LINK_RE = NOIMG + BRK + \
		r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
		# [text](url) or [text](<url>) or [text](url "title")

		compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE)

		for line_index,l in enumerate(description_l):
			if 'http' in l:
				try:
					match      = compiled_re.match(l)
					url        = match.group(9)
					text       = match.group(2)
					before_url = match.group(1)
					#title      = match.group(13)
					after_url  = match.group(14)

					l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url)
				except:
					# not used, information
					link_status = 'WARNING: the link is not in format [title](url)'
			description_l[line_index] = l

		description   = '\n'.join(description_l)
#:end

		if is_this_task_a_group(tid) and user_interface == 'web':
			subject       = '<a href="edi_web.py?tid=%s">%s -   %s</a>'%(tid,status,s)
		else:
			subject       = '%s -   %s'%(status,s)
		fg            = color_to_hex(get_forground_color(tid))
		bg            = color_to_hex(get_background_color(tid))
		r='        <tr bgcolor="#%s">\n          <td class="subject">          <font color="#%s">%s</font></td>\n          <td class="description">          <font color="#%s">%s</font></td>\n        </tr>' % (bg, fg, subject, fg, description)
	return r

## creates a string with task id, status and string parameter
# @return list of strings
# @param[in] tid task id
# @param[in] s task title string
# @ingroup EDI_CORE
# Displays a group depending on edi_core.list_option
def generate_group_string_with_tid(tid,s):
	global agenda

	status = get_status(tid)
	if (list_option == 'tids') or (list_option == 'positions'):
		# %*s to dynamically adjust id length
		r           = '%*s - %s - %s'%(ID_LENGTH,tid,status,s)
	if list_option == 'md':
		tid         = ' '
		# print group title only
		r           = '#%s'%s.replace('  0 - GROUP','')
		if agenda:
			r += '\n---\n# Agenda\n%s' % '\n\n'.join(agenda)
	if list_option == 'rst':
		tid         = ' '
		# print group title only
		title       = s.replace('  0 - GROUP','')
		r           = '='*len(title) + '\n%s\n'%title + '='*len(title) + '\n\n'
		if agenda:
			r += 'Agenda\n======\n%s\n\n' % '\n'.join(agenda)
		else:
			r += '\n.. contents::\n\n'
	if list_option == 'html':
		#<tr bgcolor="#FEFFCC">
		#  <td class="subject">          <font color="#000000">t1</font></td>
		#  <td class="description">          <font color="#000000">&nbsp;</font></td>
		#</tr>
		# display description in html
#:read_description
		# convert < to &lt; and > &gt; to ignore html tags in title line
		s             = s.replace('<','&lt;').replace('>','&gt;')
		f             = open('%s/description.txt'%generate_task_path(tid))
		# remove title line, convert < to &lt; and > &gt; to ignore html tags
		description_l = ['%s<br>'%i.rstrip().replace('<','&lt;').replace('>','&gt;') for i in f.readlines()[1:]]
		f.close()

		# convert urls to link
		NOBRACKET = r'[^\]\[]*'
		BRK = ( r'\[('
			+ (NOBRACKET + r'(\[')*6
			+ (NOBRACKET+ r'\])*')*6
			+ NOBRACKET + r')\]' )
		NOIMG = r'(?<!\!)'
		LINK_RE = NOIMG + BRK + \
		r'''\(\s*(<.*?>|((?:(?:\(.*?\))|[^\(\)]))*?)\s*((['"])(.*?)\12\s*)?\)'''
		# [text](url) or [text](<url>) or [text](url "title")

		compiled_re = re.compile("^(.*?)%s(.*?)$" % LINK_RE, re.DOTALL | re.UNICODE)

		for line_index,l in enumerate(description_l):
			if 'http' in l:
				try:
					match      = compiled_re.match(l)
					url        = match.group(9)
					text       = match.group(2)
					before_url = match.group(1)
					#title      = match.group(13)
					after_url  = match.group(14)

					l = '%s<a href="%s">%s</a>%s'% (before_url, url, text, after_url)
				except:
					# not used, information
					link_status = 'WARNING: the link is not in format [title](url)'
			description_l[line_index] = l

		description   = '\n'.join(description_l)

		subject     = '%s - %s'%(status, s)
		fg          = color_to_hex(get_forground_color(tid))
		bg          = color_to_hex(get_background_color(tid))
		r='        <tr bgcolor="#%s">\n          <td class="subject">          <font color="#%s">%s</font></td>\n          <td class="description">          <font color="#%s">%s</font></td>\n        </tr>' % (bg, fg, subject, fg, description)
	return r

## determines if task is a group
# @return empty string or 'this task is a group'
# @param[in] tid task id
# @ingroup EDI_CORE
def is_this_task_a_group(tid):
	# Identify if task is a group
	is_a_group       = ''
	# list groups
	groups           = os.listdir(data_location_groups)
	if tid in groups:
		is_a_group = 'this task is a group'
	return is_a_group

## get task title
# @return first line of task description
# @param[in] tid task id
# @ingroup EDI_CORE
def get_task_title(tid):
	task_path = generate_task_path(tid)
	f         = open('%s/description.txt'%task_path)
	r         = f.readline().strip()
	f.close()
	return r

## get creation date
# @return array of date string and unix time
# @param[in] tid task id
# @ingroup EDI_CORE
def get_creation_date(tid):
	r = []
	# Figure out creation time and modification times for description and status
	task_path         = generate_task_path(tid)
	if not os.path.exists('%s/ctime.txt'%task_path):
		task_ctime        = 'Not available'
		r.append(task_ctime)
		r.append(0)
	else:
		f = open('%s/ctime.txt'%task_path)
		task_ctime        = f.readline()
		f.close()
		r.append(task_ctime)
		r.append(time.mktime(time.strptime(task_ctime)))

	return r

## get media
# @return media file name
# @param[in] tid task id
# @ingroup EDI_CORE
def get_media(tid):
	r = ['None']
	task_path = generate_task_path(tid)
	if os.path.exists('%s/media'%task_path):
		files = os.listdir('%s/media'%task_path)
		r     = [files[0].split('.')[0], '%s/media/%s'%(task_path,files[0])]
	return r

## set media, one media file per task
# @return media file name
# @param[in] tid task id
# @param[in] filename media file to copy to task
# @ingroup EDI_CORE
def set_media(tid,filename):
	r = '%s is not a supported media file.' % filename

	task_path = generate_task_path(tid)
	if filename.strip()[-3:] == 'wav':
#:define create_media_folder
		if not os.path.exists('%s/media'%task_path):
			# create media folder
			os.mkdir('%s/media'%task_path)
#:end
		else:
			# media exists, check type
			files = os.listdir('%s/media'%task_path)
			if not files[0][-3:] == 'wav':
				r = '%s is not a sound.'%tid
				return r
		# sound file
		shutil.copy(filename,'%s/media/sound.wav'%task_path)
		r = 'sound.wav'
	if filename.strip()[-3:] == 'jpg':
#:create_media_folder
		if not os.path.exists('%s/media'%task_path):
			# create media folder
			os.mkdir('%s/media'%task_path)
		else:
			# media exists, check type
			files = os.listdir('%s/media'%task_path)
			if not files[0][-3:] == 'jpg':
				r = '%s is not an image.'%tid
				return r
		# image file
		shutil.copy(filename,'%s/media/image.jpg'%task_path)
		r = 'image.jpg'
	edi_log('added media %s in task %s'%(filename,tid))
	return r

## get attachments
# @return attachment file names
# @param[in] tid task id
# @ingroup EDI_CORE
def get_attachments(tid):
	r = ['None']
	task_path = generate_task_path(tid)
	if os.path.exists('%s/attachments'%task_path):
		files = os.listdir('%s/attachments'%task_path)
		r     = ['%s/attachments/%s'%(task_path,i) for i in files]
	return r

## set attachments
# @return attachment file names
# @param[in] tid task id
# @param[in] array of filenames
# @ingroup EDI_CORE
# With *, set_attachments copies multiple files at once
def set_attachments(tid,filenames):
	task_path = generate_task_path(tid)
	if not os.path.exists('%s/attachments'%task_path):
		# create attachment folder
		os.mkdir('%s/attachments'%task_path)

	r         = []
	for fn in filenames:
		try:
			shutil.copy(fn,'%s/attachments/'%task_path)
			edi_log('added attachment %s in task %s'%(fn,tid))
			# Remove path from fn, keep only filename
			r.append('%s/attachments/%s'%(task_path,fn.split(os.sep)[-1]))
		except:
			r.append('Failed to copy %s'%fn)
	return r

## sort task attributes (ls in edi cli)
# @return sorted task list by state
# @param[in] result from list_group
# @ingroup EDI_CORE
def sort_task_attributes(task_attributes):
	r = []
	# Keep head group on top
	if task_attributes[0]['head'] == 'head group':
		r.append(task_attributes[0])
		del task_attributes[0]
	for s in SORT_TASK_ORDER:
		for t in task_attributes:
			if t['status'] == TASK_STATUS[s]:
				r.append(t)
	return r

## sort function for sorting an array of tasks by date from newest to oldest
# @return compare result
# @param[in] result from list_group
# @ingroup EDI_CORE
def sortdatefunc(x,y):
	return cmp(y['ctime'],x['ctime'])

## sort task attributes (ls in edi cli)
# @return sorted task list by date
# @param[in] result from list_group
# @ingroup EDI_CORE
def sort_task_attributes_by_date(task_attributes):
	r = []
	# Keep head group on top
	if task_attributes[0]['head'] == 'head group':
		r.append(task_attributes[0])
		del task_attributes[0]

	# -1 to exclude the empty line
	a = task_attributes[:-1]
	a.sort(sortdatefunc)
	#print task_attributes
	r += a
	r.append(task_attributes[-1])
	return r

## list id - status - group - first line from description
# @return list of tasks and groups title
# @param[in] tid task id
# @ingroup EDI_CORE
# items in groups have the format 'ORDER_ID''TASK_ID'<br>
# task 0 is group tittle<br>
#<br>
# result array elements:<br>
# {head :string, tid :string, position :value, group :string, title :string, status :string}<br>
# head tells if the element is a group title.<br>
#<br>
# example:<br>
#fTTB1KRWfDpoSR1_ -   Active -   GROUP Motivational Interviewing<br>
#62ZvFA_q0pCZFr0Y -   Active -         news<br>
def list_group(tid):
	result      = []
	# list visible groups only
	task_status = get_status(tid)
	if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
		# List task titles in group
		group_path     = generate_group_path(tid)

		# List groups to indicate when a task is a group
		# Identify if task is a group
		groups         = os.listdir(data_location_groups)
		# End Identify if task is a group

		# print items in tid in order
		for n,fn in enumerate(sorted(os.listdir(group_path))):
			# print position in list
			current_postion = baseconvert_to_dec(fn[:ORDER_ID_LENGTH])

			# Identify if task is a group
			# Remove order_id, keep task id only
			group = '     '
			if fn[ORDER_ID_LENGTH:] in groups:
				group = 'GROUP'
			if is_linked(fn[ORDER_ID_LENGTH:]):
				group = ' LINK'
			# End Identify if task is a group
			# Get task title
			# Remove order_id, keep task id only
			task_path = generate_task_path(fn[ORDER_ID_LENGTH:])
			f         = open('%s/description.txt'%task_path)
			# Remove order_id, keep task id only
			if (not n) and (not tid == 'root'):
				# First task is group title
				# filter status, keep task if status filter is enabled
				task_status = get_status(fn[ORDER_ID_LENGTH:])
				if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
					task_d             = {}
					task_d['head']     = 'head group'
					task_d['tid']      = fn[ORDER_ID_LENGTH:]
					task_d['position'] = current_postion
					task_d['group']    = group
					task_d['title']    = f.readline().strip()
					task_d['status']   = get_status(task_d['tid'])
					task_d['ctime']    = get_creation_date(task_d['tid'])[1]
					result.append(task_d)
			else:
				# filter status, keep task if status filter is enabled
				task_status = get_status(fn[ORDER_ID_LENGTH:])
				if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
					task_d             = {}
					task_d['head']     = 'element'
					task_d['tid']      = fn[ORDER_ID_LENGTH:]
					task_d['position'] = current_postion
					task_d['group']    = group
					task_d['title']    = f.readline().strip()
					task_d['status']   = get_status(task_d['tid'])
					task_d['ctime']    = get_creation_date(task_d['tid'])[1]
					result.append(task_d)
			f.close()
		task_d             = {}
		task_d['head']     = 'empty line'
		task_d['tid']      = ''
		task_d['position'] = 0
		task_d['group']    = ''
		task_d['title']    = ''
		task_d['status']   = ''
		task_d['ctime']    = 0
		result.append(task_d)
	return result

## lists all items in the tid group
# @return list of tasks and groups title
# @param[in] tid task id
# @ingroup EDI_CORE
def list_tree(tid):
	result     = []
	# walk_group is the list of groups to visit. FIFO
	walk_group = [tid]
	# the while loop goes through all the group that are found
	while walk_group:
		# list visible groups only
		task_status = get_status(walk_group[0])
		if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
			# list items in first group
			tasks      = sorted(os.listdir(generate_group_path(walk_group[0])))
			result    += list_group(walk_group[0])

			# add group found in first group
			for t in tasks:
				# Check tasks that are not title task in a group
				if (t[ORDER_ID_LENGTH:] != walk_group[0]) and is_this_task_a_group(t[ORDER_ID_LENGTH:]):
					walk_group.append(t[ORDER_ID_LENGTH:])

		# remove first group to list items in next group
		del walk_group[0]
	return result

## generates a random task id
# @return new task id
# @ingroup EDI_CORE
def generate_id():
	return ''.join([random.choice(ID_BASE) for _ in range(ID_LENGTH)])

## converts decimal number to BASE (base 65)
# @return string representing a number in base BASE
# @param[in] n integer
# @ingroup EDI_CORE
def baseconvert(n):
	s = ""
	while 1:
		r = n % BASE
		s = ID_BASE[r] + s
		n = n / BASE
		if n == 0:
			break

	s = s.rjust(ORDER_ID_LENGTH, ',')
	return s

## converts BASE number to decimal
# @return n integer
# @param[in] n string representing a number in base BASE
# @ingroup EDI_CORE
def baseconvert_to_dec(n):
	r     = 0
	power = 1
	for digit in reversed(list(n)):
		r     += ID_BASE_STRING.find(digit) * power
		power *= BASE
	return r

## add task to group folder in database groups
# @param[in] tid task id
# @param[in] group task id
# @ingroup EDI_CORE
# Tasks are added at the top or bottom of the list.
def add_task_to_group_folder(tid,group):
	# Create an entry in group
	tasks              = sorted(os.listdir(generate_group_path(group)))
	# Add +1 to last order_id to have the task last in the list
	if tasks:
		if (len(tasks) == 1) or (add_top_or_bottom == 'bottom'):
			# add tasks in bottom
			order_id = baseconvert(baseconvert_to_dec(tasks[-1][:ORDER_ID_LENGTH])+1)
		else:
			# add tasks on top
			# temporary orderid #
			orderid_and_tid = '#' * ORDER_ID_LENGTH + tid
			if group == 'root':
				to_pos = 0
			else:
				# add new tasks after group title
				to_pos = 1

			tasks           = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:]

			# Move tasks
			path            = generate_group_path(group)
			for n,t in enumerate(tasks):
				if n == to_pos:
					# set orderid on top
					order_id = baseconvert(n)
				else:
					os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))

	else:
		# start at 0 when group is empty
		order_id = baseconvert(0)
	tid_in_groups_path = '%s/%s%s'%(generate_group_path(group),order_id,tid)
	# remove double slash that can come up
	tid_in_groups_path = tid_in_groups_path.replace('//','/')
	f                  = open(tid_in_groups_path,'w')
	f.close()

	# return tid_in_groups_path to easily add new tasks to group_directory_file_list and save time
	return tid_in_groups_path

## creates a task and opens vi
# @param[in] group task id
# @ingroup EDI_CORE
def create_task(group):
	# Open text editor
	# create task in tasks folder
#:define create_task_part1
	tid        =  generate_id()

	# Save text in tasks
	task_path  = generate_task_path(tid)
	os.mkdir(task_path)
#:end
	os.system('%s %s/description.txt' % (editor,task_path))
	if not os.path.isfile('%s/description.txt' % task_path):
		# file doesnt exist, abort task creation
		shutil.rmtree(task_path)
		return 'no new task, the description is empty'

	# Save creation time
#:define save_task_creation_time
	f          = open('%s/ctime.txt'%task_path,'w')
	(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path)
	task_mtime = time.ctime(mtime)
	f.write(task_mtime)
	f.close()
#:end

	# create status, active by default
#:define create_task_part2
	# Create status
	f          = open('%s/status'%task_path,'w')
	f.write(TASK_STATUS[TASK_STATUS_ACTIVE])
	f.close()

	tid_in_groups_path = add_task_to_group_folder(tid, group)

	global group_directory_file_list
	if group_directory_file_list:
		group_directory_file_list.append(tid_in_groups_path)

	# autolink
	global autolink
	if autolink:
		for g in autolink:
			if is_this_task_a_group(g):
				# link to groups existing in current database
				add_task_reference_to_a_group(tid,g)
#:end
	edi_log('created %s in group %s'%(tid,group))
	return tid

## create task and copy text file
# @param[in] group task id
# @param[in] text_file filename of text file to copy
# @ingroup EDI_CORE
def add_task(group,text_file):
#:create_task_part1
	tid        =  generate_id()

	# Save text in tasks
	task_path  = generate_task_path(tid)
	os.mkdir(task_path)
	shutil.copy(text_file,'%s/description.txt'%task_path)
#:save_task_creation_time
	f          = open('%s/ctime.txt'%task_path,'w')
	(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path)
	task_mtime = time.ctime(mtime)
	f.write(task_mtime)
	f.close()
#:create_task_part2
	# Create status
	f          = open('%s/status'%task_path,'w')
	f.write(TASK_STATUS[TASK_STATUS_ACTIVE])
	f.close()

	tid_in_groups_path = add_task_to_group_folder(tid, group)

	global group_directory_file_list
	if group_directory_file_list:
		group_directory_file_list.append(tid_in_groups_path)

	# autolink
	global autolink
	if autolink:
		for g in autolink:
			if is_this_task_a_group(g):
				# link to groups existing in current database
				add_task_reference_to_a_group(tid,g)
	edi_log('created %s in group %s'%(tid,group))
	return tid

## create task with description text
# @param[in] group task id
# @param[in] text string
# @ingroup EDI_CORE
def add_text(group,text):
#:create_task_part1
	tid        =  generate_id()

	# Save text in tasks
	task_path  = generate_task_path(tid)
	os.mkdir(task_path)
	f = open('%s/description.txt'%task_path,'w')
	f.write(text)
	f.close()
#:save_task_creation_time
	f          = open('%s/ctime.txt'%task_path,'w')
	(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/ctime.txt'%task_path)
	task_mtime = time.ctime(mtime)
	f.write(task_mtime)
	f.close()
#:create_task_part2
	# Create status
	f          = open('%s/status'%task_path,'w')
	f.write(TASK_STATUS[TASK_STATUS_ACTIVE])
	f.close()

	tid_in_groups_path = add_task_to_group_folder(tid, group)

	global group_directory_file_list
	if group_directory_file_list:
		group_directory_file_list.append(tid_in_groups_path)

	# autolink
	global autolink
	if autolink:
		for g in autolink:
			if is_this_task_a_group(g):
				# link to groups existing in current database
				add_task_reference_to_a_group(tid,g)
	edi_log('created %s in group %s'%(tid,group))
	return tid

## create task and copy text file, filename is task title
# @param[in] group task id
# @param[in] text_file filename of text file to copy
# @ingroup EDI_CORE
def add_task_and_filename(group,text_file):
	tid = add_task(group,text_file)

	# Add file name on first line
	f   = open('%s/description.txt'%generate_task_path(tid))
	des = f.readlines()
	f.close()
	f   = open('%s/description.txt'%generate_task_path(tid),'w')
	# save text filename only
	f.write('%s\n'%text_file.split('/')[-1])
	for l in des:
		f.write(l)
	f.close()

	return tid

## create many tasks from a text file
# @param[in] group task id
# @param[in] text_file filename of text file to copy
# @ingroup EDI_CORE
def add_many_tasks(group,text_file):
	r               = ''

	# Copy a task to text string
	# Remove '#' from first line, first character position
	# Add task to database
	f               = open(text_file)
	status          = 'start'
	text            = ''
	number_of_tasks = 0
	for l in f.readlines():
		if l.rstrip() == '---':
			if status != 'start':
				# add task to database
				add_text(group,text)
				number_of_tasks += 1
			text   = ''
			status = 'start'
		else:
			if (status == 'start') and (l[0] == '#'):
				l    = l[1:]
			text   += l
			status = 'writing task'
	f.close()
	if status == 'writing task':
		# add task to database
		add_text(group,text)
		number_of_tasks += 1

	edi_log('created %s tasks in group %s'%(number_of_tasks,group))
	r               = 'created %s tasks in group %s'%(number_of_tasks,group)
	return r

## create many one line tasks from a text file
# @param[in] group task id
# @param[in] text_file filename of text file to copy
# @ingroup EDI_CORE
def add_many_one_line_tasks(group,text_file):
	r               = []

	f               = open(text_file)
	number_of_tasks = 0
	for l in f.readlines():
		add_text(group,l.strip())
		number_of_tasks += 1
	f.close()

	edi_log('created %s tasks in group %s'%(number_of_tasks,group))
	r               = 'created %s tasks in group %s'%(number_of_tasks,group)
	return r

## create group from text file
# @param[in] group task id
# @param[in] text_file with group structure
# @ingroup EDI_CORE
def add_many_groups_from_text(group,text_file):
	r               = []

	f               = open(text_file)
	number_of_tasks = 0
	group_stack     = [group]
	for l in f.readlines():
		# count space indents
		l_l = l.split(' ')
		indent = 0
		for s in l_l:
			if s:
				break
			indent += 1
		if indent < len(group_stack)-1:
			# remove groups from stack if same level or higher levels
			del group_stack[-(len(group_stack)-1 - indent):]
		l   = l.strip()
		tid = add_text(group_stack[-1],l)
		create_group(tid)
		group_stack.append(tid)
		number_of_tasks += 1
	f.close()

	edi_log('created %s groups in group %s'%(number_of_tasks,group))
	r               = 'created %s groups in group %s'%(number_of_tasks,group)
	return r

## copy description to path using first line of description as filename
# @return path and filname
# @param[in] tid task id
# @param[in] path destination directory for task description
# @ingroup EDI_CORE
def export_task_to_a_file(tid,path):
	f   = open('%s/description.txt'%generate_task_path(tid))
	fn  = f.readline().strip()
	des = f.readlines()
	f.close()
	f   = open('%s/%s'%(path,fn),'w')
	for l in des:
		f.write(l)
	f.close()
	r = '%s/%s'%(path,fn)
	# remove eventual double //
	return r.replace(os.sep*2,os.sep)

## print description of task tid
# @return list of strings
# @param[in] tid task id
# @ingroup EDI_CORE
def display_task(tid):
	task_path = generate_task_path(tid)
	f           = open('%s/description.txt'%task_path)
	description = f.readlines()
	f.close()

	# print tid, status and first line
	description[0] = generate_task_string_with_tid(tid,description[0])
	return description

## find group containing task tid in groups folder
# @return tid
# @param[in] tid task id
# @ingroup EDI_CORE
def find_group_containing_task(tid):
	if tid == 'root':
		# root has not parent group, return root
		return tid
#:define walk_groups
	global data_location_groups
	# reuse previously created group list, to save time
	global group_directory_file_list
	if not group_directory_file_list:
		group_directory_file_list = ffind(data_location_groups)
	groups_and_tasks = group_directory_file_list
	for t in groups_and_tasks:
		if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]):
			group = t.split('/')[-2]
#:end
	try:
		a = group
	except:
		print '\n\nEDI_ERROR: Database inconsistent - run: find %s|grep %s and remove reference.\n'%(data_location, tid)
		group = 'error'
	return group

## find group containing task tid in groups folder and print
# @return list of strings
# @param[in] tid task id
# @ingroup EDI_CORE
# Shows path in tree and title for each group in path
def show_group_for_task(tid):
	global data_location_tree
	r                 = []

	# set group string to be displayed on first line
	group_s           = '     '
	if is_this_task_a_group(tid):
		group_s = 'GROUP'
	if is_linked(tid):
		group_s = ' LINK'

#:walk_groups
	global data_location_groups
	# reuse previously created group list, to save time
	global group_directory_file_list
	if not group_directory_file_list:
		group_directory_file_list = ffind(data_location_groups)
	groups_and_tasks = group_directory_file_list
	for t in groups_and_tasks:
		if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]):
			group = t.split('/')[-2]
			tree_path         = find_group_in_tree(group)
			# p is data_location folder/tree
			p_l               = data_location_tree.split('/')[-2:]
			p                 = '/'.join(p_l)
			# if empty then command is run in tree root
			if tree_path.split(p)[-1]:
				# print path of tids: tid/tid...
				r.append(tree_path.split(p)[-1][1:])
				group_titles_in_path = []
				for g in tree_path.split(p)[-1][1:].split('/'):
					group_titles_in_path.append(get_task_title(g))
				# print title/title...
				r.append('/'.join(group_titles_in_path))
			r.append(generate_group_string_with_tid(group,get_task_title(group)))
			r.append('')

	# Print media and attachments
	# media type
	media             = get_media(tid)[0]
	# attachment list
	attachments       = get_attachments(tid)

	# Print task, colors, group list
	color             = get_forground_color(tid)
	fc                = '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3])
	color             = get_background_color(tid)
	bc                = '%d,%d,%d,%d' % (color[0],color[1],color[2],color[3])
	# Figure out creation time and modification times for description and status
	task_ctime        = get_creation_date(tid)[0]

	task_path         = generate_task_path(tid)
	(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
	description_mtime = time.ctime(mtime)
	(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)
	status_mtime      = time.ctime(mtime)
	r                 = ['Task:\n%s\n\n'%generate_task_string_with_tid( tid,'%s %s'%(group_s, get_task_title(tid)) )] + ['Media type: %s'%media] + ['\nAttachments:'] + attachments + ['\n\nforeground color:        %s\nbackground color:        %s\nCreation time:           %s\nLast description change: %s\nLast status change:      %s\n\nGroup list:\n' % (fc, bc, task_ctime, description_mtime, status_mtime)] + r
	return r

## find group in tree folder
# @return path_in_tree
# @param[in] tid task id
# @ingroup EDI_CORE
def find_group_in_tree(group):
	if group == 'root':
		path_in_tree = data_location_tree
	else:
		path_in_tree = ''
		# list all group paths in tree
		f            = os.popen('find %s'%data_location_tree)
		groups       = [i.strip() for i in f.readlines()]
		f.close()
		# remove data_location_tree from paths
		del groups[0]
		# find the group in group paths
		for g in groups:
			if g.split('/')[-1] == group:
				path_in_tree = g
	return path_in_tree

## Determines if a task has multiple references
# @return integer 0 or 1
# @param[in] tid task id
# @ingroup EDI_CORE
def is_linked(tid):
	status = 0
	task_linked_groups_path = '%s/groups/' % generate_task_path(tid)
	# check if tid/groups exists
	if os.path.exists(task_linked_groups_path):
		groups = os.listdir(task_linked_groups_path)
		# check if task is linked to more than 1 group
		if len(groups) > 1:
			status = 1
	return status

## convert task to group
# @return list of stings when there is an error
# @param[in] tid task id
# @ingroup EDI_CORE
def create_group(tid):
	r                         = []
	# convert task to group only when task is not linked
	if is_linked(tid):
		r.append('Converting linked task to group removes links. The task groups are:')
		r += show_group_for_task(tid)

		# delete tid/groups because groups are not linked
		task_linked_groups_path = '%s/groups/' % generate_task_path(tid)
		groups                  = os.listdir(task_linked_groups_path)
		# remove all links except for the first group
		for g in groups[1:]:
			delete_linked_task(g,tid)
		if os.path.exists(task_linked_groups_path):
			shutil.rmtree(task_linked_groups_path)
		r.append('Created group %s in %s'%(tid,groups[0]))
	# create new group in groups folder
	os.mkdir('%s/%s'%(data_location_groups,tid))
	# First task in group is group title task
	order_id                  = baseconvert(0)
	f                         = open('%s/%s%s'%(generate_group_path(tid),order_id,tid),'w')
	f.close()

	# Add group in tree
	# update group list
	global group_directory_file_list
	group_directory_file_list = []
	group                     = find_group_containing_task(tid)
	if group == 'root':
		os.mkdir('%s/%s'%(data_location_tree,tid))
	else:
		os.mkdir('%s/%s'%(find_group_in_tree(group),tid))

	# Change status active to void by default
	# To avoid filtering groups
	if get_status(tid) == TASK_STATUS[TASK_STATUS_ACTIVE]:
		# set status to void
		set_status(tid,TASK_STATUS_VOID)
	edi_log('created group %s'%tid)
	return r

## convert group to task
# @param[in] group group to convert to task
# @ingroup EDI_CORE
def convert_group_to_task(group):
	r = []

	# list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder
	groups_and_tasks = ffind(data_location_groups)

	convert_group_status = 'delete'
	for t2 in groups_and_tasks:
		# search a task in group that is not tid
		if ('%s/'%group in t2) and (group not in t2.split('/')[-1]):
			convert_group_status = 'There is another task in the group, keep group.'
			r.append(convert_group_status)
	if (convert_group_status == 'delete') and (not 'root' in group):
		# Delete group title and group folder
		os.remove('%s/%s%s'%(generate_group_path(group),baseconvert(0),group))
		os.rmdir(generate_group_path(group))
		# Delete group in tree
		if find_group_in_tree(group):
			shutil.rmtree(find_group_in_tree(group))
		# change state from void to active
		set_status(group,TASK_STATUS_ACTIVE)
		edi_log('converted group %s to task'%group)
	return r

## delete task tid in all groups
# @return group id
# @param[in] tid task id
# @ingroup EDI_CORE
def delete_task(tid):
	# save original tid parameter for log
	tid_param        = tid
	# Delete task in tasks
	shutil.rmtree(generate_task_path(tid))

	# Delete task reference in groups
	# list all tasks (data_location_groups/GROUP/'ORDER_ID''TASK_ID') in groups folder
	groups_and_tasks = ffind(data_location_groups)

	# Identify if task is a group
	is_a_group       = is_this_task_a_group(tid)

	# Return group task, it changes when first task is deleted
	group_id         = ''
	# list groups to reorder at the end
	reorder_groups   = []
	for t in groups_and_tasks:
		# Delete reference in upper group, not the title task of the group
		if (tid == t[-ID_LENGTH:]) and (tid != t.split('/')[-2]):
			# group is the group for task tid
			group    = t.split('/')[-2]
			group_id = group
			# Delete task reference in group
			os.remove(t)
			reorder_groups.append(group_id)
			if is_a_group:
				# First task becomes a group, add a reference in group group
				# list tasks in order in tid group
				group_tasks = sorted(os.listdir(generate_group_path(tid)))
				# Delete emtpy group or first task in group becomes the group title.
				if len(group_tasks) == 1:
					shutil.rmtree(generate_group_path(tid))
					# Delete group in tree
					# when a group is deleted, subgroups are automatically deleted in the tree
					if find_group_in_tree(tid):
						shutil.rmtree(find_group_in_tree(tid))
				else:
					# Remove order_id, keep task id only
					first_task  = group_tasks[1][ORDER_ID_LENGTH:]
					group_id    = first_task
					# Create an entry in group at the same position as tid had
					order_id    = t.split('/')[-1][:ORDER_ID_LENGTH]
					f           = open('%s/%s%s'%(generate_group_path(group),order_id,first_task),'w')
					f.close()

					# Delete group task of group of more then 2 tasks, first task becomes a group
					# delete orderidtaskid
					os.remove('%s/%s'%(generate_group_path(tid),group_tasks[0]))
					os.rename('%s/%s'%(generate_group_path(tid),group_tasks[1]),'%s/%s%s'%(generate_group_path(tid),baseconvert(0),first_task))
					# reorder tasks to remove gap between group title task and first task
					path        = generate_group_path(tid)
					tasks       = sorted(os.listdir(path))
					for n,t in enumerate(tasks):
						os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
					# rename group in groups folder
					os.rename(generate_group_path(tid),generate_group_path(first_task))
					# rename group in tree folder
					group_tree_path       = find_group_in_tree(tid)
					group_tree_path_l     = group_tree_path.split('/')
					group_tree_path_l[-1] = first_task
					new_group_tree_path   = '/'.join(group_tree_path_l)
					os.rename(group_tree_path,new_group_tree_path)

	# reorder tasks to remove gaps in reorder_groups
	for tid in reorder_groups:
		path        = generate_group_path(tid)
		tasks       = sorted(os.listdir(path))
		for n,t in enumerate(tasks):
			os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))

	# Return group task
	edi_log('deleted %s in %s'%(tid_param,' '.join(reorder_groups)))
	return group_id

## Delete task only if it is linked in one group
# @return group id
# @param[in] group task id
# @param[in] tid task id
# @ingroup EDI_CORE
def delete_linked_task(group,tid):
	group_id = group
	if not is_linked(tid):
		group_id = delete_task(tid)
	else:
		# Delete task reference in group
		# find task in group: ORDER_IDTASK_ID
		tasks = os.listdir(generate_group_path(group))
		for t in tasks:
			if t[ORDER_ID_LENGTH:] == tid:
				os.remove('%s%s' % (generate_group_path(group), t))

		# delete group in tid/groups
		os.remove('%s/groups/%s' % (generate_task_path(tid), group))

		# reorder tasks to remove gaps in group
		path  = generate_group_path(group)
		tasks = os.listdir(path)
		for n,t in enumerate(tasks):
			os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))

		edi_log('deleted %s in group %s'%(tid,group))

	return group_id

## delete group tid
# @return group id
# @param[in] tid task id
# @ingroup EDI_CORE
def delete_group(tid):
	# Delete tasks in group
	group_tasks = sorted(os.listdir(generate_group_path(tid)))
	if (len(group_tasks) == 0) and (tid == 'root'):
		# the database is already empty
		return tid
	if tid != 'root':
		# root group doesnt have a title
		# Remove group title from the loop to delete tasks only and then group
		del group_tasks[0]

	# Delete tasks and groups recursively
	for t in group_tasks:
		# Remove order_id, keep task id only
		oid = t[ORDER_ID_LENGTH:]
		if not is_this_task_a_group(oid):
			# delete tasks that are linked only once
			delete_linked_task(tid, oid)
		else:
			delete_group(oid)

	if tid != 'root':
		# never delete root group
		return delete_task(tid)
	else:
		# return root and keep root task
		return tid

## edit task with vi
# @param[in] tid task id
# @ingroup EDI_CORE
def edit_task(tid):
	os.system('%s %s/description.txt' % (editor, generate_task_path(tid)))
	edi_log('edited %s'%tid)

## move task from group at at_pos to to_pos and reorder
# @return list of stings when there is an error
# @param[in] group task id
# @param[in] at_pos selected position
# @param[in] to_pos insert position
# @ingroup EDI_CORE
def change_task_order(group,at_pos,to_pos):
	# save original group parameter for log
	group_param     = group
	# List tasks in group
	r               = []
	path            = generate_group_path(group)
	tasks           = sorted(os.listdir(path))

	# Verify position
	# Get task
	try:
		orderid_and_tid = tasks[at_pos]
	except:
		r.append('%d is an invalid position.'%at_pos)
		return r
	if to_pos == 0:
		tid = orderid_and_tid[ORDER_ID_LENGTH:]
		# do not move linked tasks to position 0
		if is_linked(tid):
			r.append('Converting linked task to group removes links. The task groups are:')
			r += show_group_for_task(tid)

			# delete tid/groups because groups are not linked
			task_linked_groups_path = '%s/groups/' % generate_task_path(tid)
			groups                  = os.listdir(task_linked_groups_path)
			# remove all links except for the first group
			for g in groups:
				if not g == group:
					delete_linked_task(g,tid)
			if os.path.exists(task_linked_groups_path):
				shutil.rmtree(task_linked_groups_path)
			parent_group             = find_group_containing_task(group)
			r.append('Created group %s in %s'%(tid,parent_group))
		# do not move groups to position 0
		if is_this_task_a_group(tid):
			r.append('Having a group in group title is not supported.')
			return r
	# Insert task at to_pos
	if to_pos > at_pos:
		# +1 because at_pos reference will be deleted and will shift to_pos reference
		to_pos += 1
		tasks   = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:]
		# Delete task at at_pos
		del tasks[at_pos]
	else:
		# rename group title, when to_pos in 0
		if (to_pos == 0) and (not group == 'root'):
			to_pos_tid = tasks[to_pos][ORDER_ID_LENGTH:]

		tasks   = tasks[:to_pos] + [orderid_and_tid] + tasks[to_pos:]
		# Delete task at at_pos+1 because the new position is before at_pos
		# at_pos was shifted when to_pos reference was added above
		del tasks[at_pos+1]

#:define reorder_tasks
	# Move tasks
	for n,t in enumerate(tasks):
		os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
#:end

	# rename group title, when to_pos in 0
	if (to_pos == 0) and (not group == 'root'):
		# rename group in parent group
		# group is tid for parent group and to_pos_tid is equal to group input parameter because to_pos is 0
		group = find_group_containing_task(group)
		# to_pos_orderid_and_tid is group in parent group
		parent_group_tasks = os.listdir(generate_group_path(group))
		for t in parent_group_tasks:
			if t[ORDER_ID_LENGTH:] == to_pos_tid:
				to_pos_orderid_and_tid = t
		# remove order_id, keep task id only for new group
		group_id    = orderid_and_tid[ORDER_ID_LENGTH:]
		# create an entry in parent group at the same position as to_pos tid had
		order_id    = to_pos_orderid_and_tid[:ORDER_ID_LENGTH]
		f           = open('%s/%s%s'%(generate_group_path(group),order_id,group_id),'w')
		f.close()

		# delete group task of group
		# delete orderidtaskid
		os.remove('%s/%s'%(generate_group_path(group),to_pos_orderid_and_tid))

		# rename group in groups folder
		os.rename(generate_group_path(to_pos_tid),generate_group_path(group_id))
		# rename group in tree folder
		group_tree_path       = find_group_in_tree(to_pos_tid)
		group_tree_path_l     = group_tree_path.split('/')
		group_tree_path_l[-1] = group_id
		new_group_tree_path   = '/'.join(group_tree_path_l)
		os.rename(group_tree_path,new_group_tree_path)

		# rename group in linked task and former linked task with a 'groups' folder in tasks database folder
		group_tasks = os.listdir(generate_group_path(group_id))
		for group_task in group_tasks:
			if os.path.exists('%s/groups' % generate_task_path(group_task[ORDER_ID_LENGTH:])):
				# search for to_pos_tid (group input parameter) in task path groups folder
				link_groups = os.listdir('%s/groups' % generate_task_path(group_task[ORDER_ID_LENGTH:]))
				for lg in link_groups:
					if to_pos_tid == lg:
						# rename group to new group id since the title changed
						os.rename('%s/groups/%s' % (generate_task_path(group_task[ORDER_ID_LENGTH:]),to_pos_tid), '%s/groups/%s' % (generate_task_path(group_task[ORDER_ID_LENGTH:]),group_id))

	edi_log('changed task order %s to %s in group %s'%(at_pos, to_pos, group_param))
	return r


## move task to group
# @return tid
# @param[in] tgroup task id, select a task in this group
# @param[in] tid task id
# @param[in] group task id, destination group
# @ingroup EDI_CORE
def move_task_to_a_group(tgroup,tid,group):

	# find task tid in tgroup and remove task
	path            = generate_group_path(tgroup)
	tasks     	= sorted(os.listdir(path))
	task_in_group   = ''
	for n,t in enumerate(tasks):
		# remove task from tasks to reorder tgroup
		if t[ORDER_ID_LENGTH:] == tid:
			task_in_group = t
			del tasks[n]
			break
	if not task_in_group:
		return '%s not found in %s'%(tid,tgroup)

	# Move group in tree
	if is_this_task_a_group(tid):
		if find_group_in_tree(tid)[:-ID_LENGTH-1] == find_group_in_tree(group):
			# prevent moving a group on itself, in tree and moving group title position to parent group
			return tid
		shutil.move(find_group_in_tree(tid),find_group_in_tree(group))

	# Remove task in source group
	os.remove('%s/%s'%(path,task_in_group))

#:reorder_tasks
	# Move tasks
	for n,t in enumerate(tasks):
		os.rename('%s/%s'%(path,t),'%s/%s%s'%(path,baseconvert(n),t[ORDER_ID_LENGTH:]))
	# check that tid is not already linked in destination group
	if is_linked(tid):
		link_groups = os.listdir('%s/groups/' % generate_task_path(tid))
		if group in link_groups:
			# removed task reference from tgroup. Remove tgroup reference in task. There is already a tid reference in group, nothing more to do
			os.remove('%s/groups/%s' % (generate_task_path(tid), tgroup))
			return tid

	# Create reference in destination group
	add_task_to_group_folder(tid, group)

	# linked tasks, remove source group and add destination group
	if is_linked(tid):
		# remove source tgroup from tid/groups
		os.remove('%s/groups/%s' % (generate_task_path(tid), tgroup))
		f         = open('%s/groups/%s'%(generate_task_path(tid), group),'w')
		f.close()

	edi_log('moved %s in group %s to group %s'%(tid,tgroup,group))
	return tid

## copy task to group with new tid
# @return new tid
# @param[in] tid task id
# @param[in] group task id, destination group
# @ingroup EDI_CORE
def copy_task_to_a_group(tid,group):
	# Generate new tid
	newtid = generate_id()
	# Copy task in tasks
	shutil.copytree(generate_task_path(tid),generate_task_path(newtid))
	# delete tid/groups because new task is not linked
	task_linked_groups_path = '%s/groups/' % generate_task_path(newtid)
	if os.path.exists(task_linked_groups_path):
		shutil.rmtree(task_linked_groups_path)
	# Copy group in groups and in tree
	if is_this_task_a_group(tid):
		# create new group
		shutil.copytree(generate_group_path(tid),generate_group_path(newtid))
		# Change group title task to newtid
		shutil.move('%s/%s%s'%(generate_group_path(newtid),baseconvert(0),tid),'%s/%s%s'%(generate_group_path(newtid),baseconvert(0),newtid))

		# delete tasks to be recreated with new tid
		tasks = sorted(os.listdir(generate_group_path(newtid)))
		del tasks[0]
		for t in tasks:
			os.remove('%s/%s'%(generate_group_path(newtid),t))

		# Add group in tree
		if group == 'root':
			os.mkdir('%s/%s'%(data_location_tree,newtid))
		else:
			os.mkdir('%s/%s'%(find_group_in_tree(group),newtid))


	# Add reference in group
	tmp_tid = tid
	tid     = newtid
	# Create reference in destination group
	add_task_to_group_folder(tid, group)
	tid     = tmp_tid

	if is_this_task_a_group(tid):
		# walk in group
		# list items in group
		tasks      = sorted(os.listdir(generate_group_path(tid)))

		# add group found in first group
		for t in tasks:
			# Check tasks that are not title task in a group
			if t[ORDER_ID_LENGTH:] != tid:
				# copy_task_to_a_group recursively
				copy_task_to_a_group(t[ORDER_ID_LENGTH:],newtid)
	edi_log('copied %s to group %s, created %s'%(tid,group,newtid))
	return newtid

## create task path in database using database name
# @return path to task in tasks
# @param[in] tid task id
# @param[in] location database name in data section of easydoneit.ini
# @ingroup EDI_CORE
def generate_task_path_in_database(tid,location):
	global selected
	global selected_path

	z              = dict(zip(selected, selected_path))
	location_tasks = '%s/tasks'%z[location]
	return '%s/%s'%(location_tasks,tid)

## create group path in database using database name
# @return path to group in groups
# @param[in] tid task id
# @param[in] location database name in data section of easydoneit.ini
# @ingroup EDI_CORE
def generate_group_path_in_database(tid,location):
	global selected
	global selected_path

	z              = dict(zip(selected, selected_path))
	location_groups = '%s/groups'%z[location]
	return '%s/%s/'%(location_groups,tid)

## find group in tree folder in database
# @return path to group in tree
# @param[in] group task id
# @param[in] location database name in data section of easydoneit.ini
# @ingroup EDI_CORE
def find_group_in_tree_in_database(group,location):
	global selected
	global selected_path

	z              = dict(zip(selected, selected_path))
	location_tree  = '%s/tree'%z[location]

	if group == 'root':
		path_in_tree = location_tree
	else:
		path_in_tree = ''
		# list all group paths in tree
		f            = os.popen('find %s'%location_tree)
		groups       = [i.strip() for i in f.readlines()]
		f.close()
		# remove data_location_tree from paths
		del groups[0]
		# find the group in group paths
		for g in groups:
			if g.split('/')[-1] == group:
				path_in_tree = g
	return path_in_tree

## add task with new tid in selected database
# @param[in] tid task id
# @param[in] group task id
# @param[in] location database name in data section of easydoneit.ini
# @ingroup EDI_CORE
def add_task_to_group_folder_in_database(tid,group,location):
	# Create an entry in group
	tasks     = sorted(os.listdir(generate_group_path_in_database(group,location)))
	# Add +1 to last order_id to have the task last in the list
	if tasks:
		order_id = baseconvert(baseconvert_to_dec(tasks[-1][:ORDER_ID_LENGTH])+1)
	else:
		# start at 0 when group is empty
		order_id = baseconvert(0)
	f         = open('%s/%s%s'%(generate_group_path_in_database(group,location),order_id,tid),'w')
	f.close()

## copy task to group in selected database with new tid
# @return new tid
# @param[in] tid task id
# @param[in] group task id
# @param[in] location database name in data section of easydoneit.ini
# @ingroup EDI_CORE
def copy_task_to_database(tid,location,group):
	# Generate new tid
	newtid = generate_id()
	# Copy task in tasks
	shutil.copytree(generate_task_path(tid),generate_task_path_in_database(newtid,location))
	# delete tid/groups because new task is not linked
	task_linked_groups_path = '%s/groups/' % generate_task_path_in_database(newtid,location)
	if os.path.exists(task_linked_groups_path):
		shutil.rmtree(task_linked_groups_path)
	# Copy group in groups and in tree
	if is_this_task_a_group(tid):
		# create new group
		shutil.copytree(generate_group_path(tid),generate_group_path_in_database(newtid,location))
		# Change group title task to newtid
		shutil.move('%s/%s%s'%(generate_group_path_in_database(newtid,location),baseconvert(0),tid),'%s/%s%s'%(generate_group_path_in_database(newtid,location),baseconvert(0),newtid))

		# delete tasks to be recreated with new tid
		tasks = sorted(os.listdir(generate_group_path_in_database(newtid,location)))
		del tasks[0]
		for t in tasks:
			os.remove('%s/%s'%(generate_group_path_in_database(newtid,location),t))

		# Add group in tree
		global selected
		global selected_path

		z              = dict(zip(selected, selected_path))
		location_tree  = '%s/tree'%z[location]
		if group == 'root':
			os.mkdir('%s/%s'%(location_tree,newtid))
		else:
			os.mkdir('%s/%s'%(find_group_in_tree_in_database(group,location),newtid))


	# Add reference in group
	# Create reference in destination group
	add_task_to_group_folder_in_database(newtid, group, location)

	if is_this_task_a_group(tid):
		# walk in group
		# list items in group
		tasks      = sorted(os.listdir(generate_group_path(tid)))

		# add group found in first group
		for t in tasks:
			# Check tasks that are not title task in a group
			if t[ORDER_ID_LENGTH:] != tid:
				# copy_task_to_database recursively
				copy_task_to_database(t[ORDER_ID_LENGTH:],location,newtid)
	return newtid

## move task to database/group
# @param[in] tgroup group id for task tid
# @param[in] tid task id
# @param[in] location database name in data section of easydoneit.ini
# @param[in] group destination group id
# @ingroup EDI_CORE
# copy and delete
def move_task_to_a_group_to_database(tgroup,tid,location,group):
	newtid = copy_task_to_database(tid,location,group)
	if is_this_task_a_group(tid):
		delete_group(tid)
	else:
		delete_linked_task(tgroup,tid)
	return newtid

## add reference to tid in group
# @param[in] tid task id
# @param[in] group destination group id
# @ingroup EDI_CORE
# Used in edi ln to link tasks
def add_task_reference_to_a_group(tid,group):
	prints = []
	if is_this_task_a_group(tid):
		prints.append('Select a task to link instead of a group')
	else:
		# add group to task folder
		task_path = generate_task_path(tid)
		if not os.path.exists('%s/groups/' % task_path):
			os.mkdir('%s/groups/' % task_path)

			# add first group when task is linked to tid/groups/
			first_group = find_group_containing_task(tid)
			f         = open('%s/groups/%s'%(task_path,first_group),'w')
			f.close()
		f         = open('%s/groups/%s'%(task_path,group),'w')
		f.close()

		# add task to group
		add_task_to_group_folder(tid, group)

	edi_log('linked %s to group %s'%(tid,group))
	return prints

## set status for tid
# @param[in] tid task id
# @param[in] status_number index in edi_core.TASK_STATUS
# @ingroup EDI_CORE
def set_status(tid,status_number):
	# Change status
	f         = open('%s/status'%generate_task_path(tid),'w')
	f.write(TASK_STATUS[status_number])
	f.close()
	edi_log('set status for %s to %s'%(tid,TASK_STATUS[status_number].strip()))

## set all tasks in group to active
# @param[in] tid task id
# @ingroup EDI_CORE
def reset_group_status(tid):
	group_tasks = os.listdir(generate_group_path(tid))
	for t in group_tasks:
		if (not is_this_task_a_group(t[ORDER_ID_LENGTH:])) or (is_this_task_a_group(t[ORDER_ID_LENGTH:]) and (get_status(t[ORDER_ID_LENGTH:]) != TASK_STATUS[TASK_STATUS_VOID])):
			set_status(t[ORDER_ID_LENGTH:],TASK_STATUS_ACTIVE)
	edi_log('reset group %s'%tid)

## search string in tasks folder
# @param[in] search query string
# @ingroup EDI_CORE
def search_string(search):
	global data_location_tasks
	global status_filters_d
	global STATUS_FILTER_STATES

	tasks  = sorted(os.listdir(data_location_tasks))

	# search in descriptions only
	all_grep_r = []
	for tid in tasks:
		if tid != 'root':
			# search in visible tasks only (filter enable status)
			task_status = get_status(tid)
			if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
				f           = os.popen('grep -i -R "%s" %s/description.txt'%(search,generate_task_path(tid)))
				# add tid and filename to results
				grep_r      = ['/%s/description.txt:%s'%(tid,i) for i in f.readlines()]
				f.close()
				all_grep_r  += grep_r

	grep_r     = all_grep_r

	# r lists all hits
	r      = []
	for l in grep_r:
		# replace filename with tid and title (first line in file)
		# read first line in file
		l_l          = l.split(':')
		hit_filename = l_l[0]
		f            = open('%s%s'%(data_location_tasks,hit_filename))
		title        = f.readline().strip()
		f.close()
		# set tid
		tid          = hit_filename.split('/')[1]
		group = '     '
		if is_this_task_a_group(tid):
			group = 'GROUP'
		if is_linked(tid):
			group = ' LINK'
		l_l[0]       = '%s %s - %s'%(tid,group,title)
		l            = ':'.join(l_l)
		r.append(l)

	return r

## search string in group
# @param[in] group task id
# @param[in] search query string
# @ingroup EDI_CORE
def search_string_in_group(group,search):

	tasks  = sorted(os.listdir(generate_group_path(group)))

	# r lists all hits
	r      = []
	for orderidtid in tasks:
		# tid for current task in group
		tid         = orderidtid[ORDER_ID_LENGTH:]
		# search in visible tasks only (filter enable status)
		task_status = get_status(tid)
		grep_r      = []
		if status_filters_d[task_status] == STATUS_FILTER_STATES[0]:
			f           = os.popen('grep -i -R "%s" %s/description.txt'%(search,generate_task_path(tid)))
			# add tid and filename to results
			grep_r      = ['/%s/description.txt:%s'%(tid,i) for i in f.readlines()]
			f.close()

		for l in grep_r:
			# replace filename with tid and title (first line in file)
			# read first line in file
			l_l          = l.split(':')
			hit_filename = l_l[0]
			f            = open('%s%s'%(data_location_tasks,hit_filename))
			title        = f.readline().strip()
			f.close()
			# set tid
			tid          = hit_filename.split('/')[1]
			group = '     '
			if is_this_task_a_group(tid):
				group = 'GROUP'
			if is_linked(tid):
				group = ' LINK'
			l_l[0]       = '%s %s - %s'%(tid,group,title)
			l            = ':'.join(l_l)
			r.append(l)

	return r

## search string in tree
# @param[in] group task id
# @param[in] search query string
# @ingroup EDI_CORE
def search_string_in_tree(group,search):
	# walk_group is the list of groups to visit. FIFO
	r          = []
	walk_group = [group]
	# the while loop goes through all the group that are found
	while walk_group:
		# list items in first group
		tasks      = sorted(os.listdir(generate_group_path(walk_group[0])))
		r += search_string_in_group(walk_group[0],search)

		# add group found in first group
		for t in tasks:
			# Check tasks that are not title task in a group
			if (t[ORDER_ID_LENGTH:] != walk_group[0]) and is_this_task_a_group(t[ORDER_ID_LENGTH:]):
				walk_group.append(t[ORDER_ID_LENGTH:])

		# remove first group to list items in next group
		del walk_group[0]
	return r

## show tree
#  @ingroup EDI_CORE
# print all trees: group tids and titles
def show_tree():
	r       = []
	f       = os.popen('cd %s;find .'%data_location_tree)
	# remove first empty line
	tidtree = f.readlines() [1:]
	f.close()

	# remove './' from path
	tidtree = [i[2:] for i in tidtree]

	# print titles path\n group tid path\n\n
	for l in tidtree:
		# find title for group tids
		group_titles_in_path = []
		for g in l.strip().split(os.sep):
			group_titles_in_path.append(get_task_title(g))
		if user_interface == 'web':
			# convert / to - to be able to create links correctly
			group_titles_in_path = [i.replace('/', '-') for i in group_titles_in_path]
		# create string title/title...
		group_titles_in_path_s = '/'.join(group_titles_in_path)
		r.append('%s\n'%group_titles_in_path_s)
		r.append('%s\n'%l)

	return r

## group statistics
#  @ingroup EDI_CORE
# print statistics for a group recursively
def group_statistics(group):
	global stats
	global stats_total
	global stats_creation_dates
	global stats_overtime

	# compute total number of tasks in group
	path = generate_group_path(group)

	tasks         = []
	# remove group title
	for i in os.listdir(path):
		if not baseconvert(0) in i[:ORDER_ID_LENGTH]:
			tasks.append(i[ORDER_ID_LENGTH:])

	stats_total   += len(tasks)

	# compute number of tasks in each state, groups and links
	for tid in tasks:
		task_status        = get_status(tid)
		stats[task_status] += 1
		if is_this_task_a_group(tid):
			stats['   Group'] += 1
			group_statistics(tid)
		if is_linked(tid):
			stats['  Linked'] += 1

		#stats_creation_dates
		# Figure out creation time and modification time for description
		task_ctime         = get_creation_date(tid)[1]

		task_path         = generate_task_path(tid)
		(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
		# 'if' below to be compatible with first database format
		if task_ctime == 0:
			# ctime not available
			task_ctime = mtime
		stats_creation_dates.append(task_ctime)

		(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)

		cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime))
		sdate = time.strftime("%Y-%m-%d", time.localtime(mtime))
		if not stats_overtime.has_key(cdate):
			# initialize date dict
			stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
		stats_overtime[cdate]['Creation'] += 1
		if not stats_overtime.has_key(sdate):
			# initialize date dict
			stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
		stats_overtime[sdate][task_status] += 1
		#end

## statistics
#  @ingroup EDI_CORE
# print statistics for a group or a database
# compute speed
def statistics(group):
	global stats
	global stats_total
	global stats_creation_dates
	global stats_overtime
	r = []

	# initialize stats dictionary
	stat_keys     = [TASK_STATUS[i] for i in range(len(TASK_STATUS))]
	stat_keys.append('   Group')
	stat_keys.append('  Linked')
	state_amounts = [0 for i in range(len(stat_keys))]
	stats         = dict(zip(stat_keys,state_amounts))

	if group == 'for database':
		# compute total number of tasks, excluding root
		path          = data_location_tasks
		tasks         = []
		for i in os.listdir(path):
			if i != 'root':
				tasks.append(i)

		# compute number of tasks in each state, groups and links
		for tid in tasks:
			task_status        = get_status(tid)
			stats[task_status] += 1
			if is_this_task_a_group(tid):
				stats['   Group'] += 1
			if is_linked(tid):
				stats['  Linked'] += 1

#:define stats_creation_dates
			# Figure out creation time and modification time for description
			task_ctime         = get_creation_date(tid)[1]

			task_path         = generate_task_path(tid)
			(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
			if task_ctime == 0:
				# ctime not available
				task_ctime = mtime
			stats_creation_dates.append(task_ctime)

			(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)

			cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime))
			sdate = time.strftime("%Y-%m-%d", time.localtime(mtime))
			if not stats_overtime.has_key(cdate):
				# initialize date dict
				stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
			stats_overtime[cdate]['Creation'] += 1
			if not stats_overtime.has_key(sdate):
				# initialize date dict
				stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
			stats_overtime[sdate][task_status] += 1
#:end

		stats_total   = len(tasks)
	else:
		# compute total number of tasks in group
		path = generate_group_path(group)

		tasks         = []
		for i in os.listdir(path):
			if group == 'root':
				tasks.append(i[ORDER_ID_LENGTH:])
			else:
				# the group task is not counted in the statistics
				if not baseconvert(0) in i[:ORDER_ID_LENGTH]:
					tasks.append(i[ORDER_ID_LENGTH:])

		stats_total   = len(tasks)

		# compute number of tasks in each state, groups and links, recursively
		for tid in tasks:
			task_status        = get_status(tid)
			stats[task_status] += 1
			if is_this_task_a_group(tid):
				stats['   Group'] += 1
				group_statistics(tid)
			if is_linked(tid):
				stats['  Linked'] += 1

#:stats_creation_dates
			# Figure out creation time and modification time for description
			task_ctime         = get_creation_date(tid)[1]

			task_path         = generate_task_path(tid)
			(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/description.txt'%task_path)
			if task_ctime == 0:
				# ctime not available
				task_ctime = mtime
			stats_creation_dates.append(task_ctime)

			(mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime) = os.stat('%s/status'%task_path)

			cdate = time.strftime("%Y-%m-%d", time.localtime(task_ctime))
			sdate = time.strftime("%Y-%m-%d", time.localtime(mtime))
			if not stats_overtime.has_key(cdate):
				# initialize date dict
				stats_overtime[cdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
			stats_overtime[cdate]['Creation'] += 1
			if not stats_overtime.has_key(sdate):
				# initialize date dict
				stats_overtime[sdate] = dict(zip(STATS_OVERTIME_KEYS,[0 for i in range(len(STATS_OVERTIME_KEYS))]))
			stats_overtime[sdate][task_status] += 1

	if not stats_total:
		r.append('0 task in statistics.')
		return r

	# csv format, not used for now
	#col_names = ['Tasks'] + sorted(stats.keys())
	#col_names = [i strip for i in col_names]
	#print ','.join(col_names)
	#csv = '%d'%len(tasks)
	#for k in sorted(stats.keys())
	#	csv += ',%d'%stats[k]
	#r append csv
	#r append ''

	r.append('Number of items: %d\n'%stats_total)
	for k in sorted(stats.keys()):
		r.append('%s - %d'%(k, stats[k]))
	r.append('')

	# speed
	start_time        = sorted(stats_creation_dates)[0]
	now               = time.time()
	done_and_inactive = stats[TASK_STATUS[TASK_STATUS_DONE]] + stats[TASK_STATUS[TASK_STATUS_INACTIVE]]
	if not done_and_inactive:
		r.append('Nothing is done or inactive')
	else:
		# subtract tasks in state void, because tasks in void state are informative or groups
		number_of_tasks   = stats_total - stats[TASK_STATUS[TASK_STATUS_VOID]]
		remaining_tasks   = number_of_tasks - done_and_inactive
		remaining_time    = (now - start_time) / float(done_and_inactive) * remaining_tasks
		r.append('Number of tasks (excluding voids): %d'%number_of_tasks)
		r.append('Remaining tasks:                   %d'%remaining_tasks)
		r.append('Remaining days:                    %.3f'%(remaining_time/86400))
		r.append('Start date:                        %s'%time.strftime("%Y-%m-%d", time.localtime(start_time)))
		r.append("Today's date:                      %s"%time.strftime("%Y-%m-%d", time.localtime(now)))
		r.append('Finish date:                       %s'%time.strftime("%Y-%m-%d", time.localtime(now + remaining_time)))

	# create csv stats from stats_overtime
	f = open('%s/stats.csv'%data_location,'w')
	f.write('date,%s\n'%','.join([i.strip() for i in STATS_OVERTIME_KEYS]))

	for d in sorted(stats_overtime.keys()):
		f.write('%s,%s\n'%(d, ','.join([str(stats_overtime[d][i]) for i in STATS_OVERTIME_KEYS])))
	f.close()

	# create creation, done and inactive stats
	f = open('%s/stats_creation_done_inactive.csv'%data_location,'w')
	f.write('date,Creation,Done/Inactive\n')

	for d in sorted(stats_overtime.keys()):
		f.write('%s,%d,%d\n'%(d, stats_overtime[d]['Creation'], stats_overtime[d][TASK_STATUS[TASK_STATUS_DONE]] + stats_overtime[d][TASK_STATUS[TASK_STATUS_INACTIVE]]))
	f.close()

	r.append('')
	return r

# test core functions
#def test
#	# test
#	list_group('root')
#
#	t1 = add_task('root', 'task1.txt')
#	add_task('root', 'task2.txt')
#	g3 = add_task('root', 'task3.txt')
#
#	create_group(g3)
#	t4 = add_task(g3, 'task4.txt')
#
#	list_group('root')
#	list_group(g3)
#
#	#display_task(t1)
#	#display_task(t4)
#
#	# Delete task in a group of 2 tasks
#	print '# Delete task in a group of 2 tasks'
#	delete_task(t4)
#
#	list_group('root')
#
#	create_group(g3)
#	t4 = add_task(g3, 'task4.txt')
#
#	list_group('root')
#
#	# Delete group first task
#	print '# Delete group first task'
#	delete_task(g3)
#
#	list_group('root')
#
#	# Delete group task of a group with more than 2 tasks
#	print '# Delete group task of a group with more than 2 tasks'
#	delete_task(t4)
#
#	g3 = add_task('root', 'task3.txt')
#	create_group(g3)
#	t4 = add_task(g3, 'task4.txt')
#	t2 = add_task(g3, 'task2.txt')
#
#	list_group('root')
#	list_group(g3)
#	new_group_id = delete_task(g3)
#	print 'New group id %s'%new_group_id
#
#	list_group('root')
#	list_group(new_group_id)
#
#	# Delete group
#	print '# Delete group'
#	delete_group(new_group_id)
#
#	list_group('root')
#
#	print baseconvert(100)
#	print baseconvert_to_dec(baseconvert(100))
#	print
#
#	#edit_task(t1)
#
#	# Change order
#	print '# Change order'
#	g3 = add_task('root', 'task3.txt')
#	change_task_order('root',1,0)
#	change_task_order('root',0,2)
#
#	list_group('root')
#
#	# Change status
#	print '# Change status'
#	create_group(g3)
#	t4 = add_task(g3, 'task4.txt')
#	t2 = add_task(g3, 'task2.txt')
#
#	list_group('root')
#
#	set_status(t1,TASK_STATUS_DONE)
#	set_status(t4,TASK_STATUS_DONE)
#
#	list_group('root')
#	list_group(g3)
#
#	# Reset status
#
#	reset_group_status(g3)
#
#	list_group('root')
#	list_group(g3)
#
#	reset_group_status('root')
#
#	list_group('root')
#	list_group(g3)
#
#	# Create a group in group
#	print '# Create a group in group'
#	create_group(t4)
#	t2 = add_task(t4,'task2.txt')
#
#	list_group('root')
#	list_group(g3)
#	list_group(t4)
#
#	# Delete task in a group of 2 tasks not in root
#	print '# Delete task in a group of 2 tasks not in root'
#	delete_task(t2)
#
#	list_group('root')
#	list_group(g3)
#
#	# Delete group with groups in it
#	print '# Delete group with groups in it'
#	create_group(t4)
#	t2 = add_task(t4,'task2.txt')
#
#	list_group('root')
#	list_group(g3)
#	list_group(t4)
#
#	delete_group(g3)
#
#	list_group('root')
#
#	# Search string
#	print '# Search string'
#
#	search_string('AND')
#
#	# List tree
#	print '# List tree'
#	g3 = add_task('root', 'task3.txt')
#	create_group(g3)
#	t4 = add_task(g3, 'task4.txt')
#	t2 = add_task(g3, 'task2.txt')
#	create_group(t4)
#	t2 = add_task(t4,'task2.txt')
#
#	list_tree('root')
#
#	# Show group for a task
#	print '# Show group for a task'
#
#	print 'Show group for %s - %s' %(t2,get_task_title(t2))
#	show_group_for_task(t2)
#	print 'Show group for %s - %s' %(t4,get_task_title(t4))
#	show_group_for_task(t4)
#	print 'Show group for %s - %s' %(t1,get_task_title(t1))
#	show_group_for_task(t1)

	# create task
	#create_task(t4)
	#list_group(t4)

## start - always called at startup.
# @ingroup EDI_CORE
# @param[in] interface selects current user interface. cli loads the configuration from user home, web loads the configuration located in edi_web folder.
# creates default .easydoneit.ini<br>
# creates database folders<br>
# loads .easydoneit.ini
def start(interface='cli'):
	global user_interface
	user_interface = interface

	if interface=='web':
		# Do not try to access ~/.easydoneit.ini
		inipath = '.easydoneit.ini'
	else:
		inipath = '~/.easydoneit.ini'
		# create ~/.easydoneit.ini if it doesnt exist, (not in web interface mode)
		if not os.path.isfile(os.path.expanduser('~/.easydoneit.ini')):
			f = open(os.path.expanduser('~/.easydoneit.ini'),'w')
			f.write('[data]\n')
			f.write('location=~/easydoneit_data\n')
			f.write('1=~/easydoneit_data\n')
			f.write('\n')
			f.write('[locations]\n')
			f.write('selected=1\n')
			f.write('default_add_in=1\n')
			f.write('\n')
			f.write('[filters]\n')
			# enable all status filters
			fi = zip(TASK_STATUS,status_filters)
			for name,nfilter in fi:
				# strip to remove spaces in status strings
				f.write("%s=%s\n"%(name.strip(),nfilter))
			f.write('\n')
			f.write('[colors]\n')
			co = zip(TASK_STATUS,status_fgColors)
			for name,color in co:
				f.write('%s_fgColor=%d,%d,%d,%d\n' % (name.strip(),color[0],color[1],color[2],color[3]))
			co = zip(TASK_STATUS,status_bgColors)
			for name,color in co:
				f.write('%s_bgColor=%d,%d,%d,%d\n' % (name.strip(),color[0],color[1],color[2],color[3]))

			f.write('\n[settings]\n')
			f.write('editor=vi\n')

			f.close()
			# default permissions 600 rw for user no access for group and world
			os.chmod(os.path.expanduser('~/.easydoneit.ini'),stat.S_IRUSR|stat.S_IWUSR)

	# load config from inipath
	config              = ConfigParser.ConfigParser()
	config.readfp(open(os.path.expanduser(inipath)))

	# convert ~ to home path
	global data_location
	data_location       = os.path.expanduser(config.get('data','location'))

	# load available databases
	global databases
	data_section        = config.items('data')
	# remove location which is a path to a database
	DATABASE_NAME = 0
	DATABASE_PATH = 1
	# clear databases for reentrant testing
	databases = []
	for d in data_section:
		if d[DATABASE_NAME] != 'location':
			databases.append(d)

	# load add_top_or_bottom
	if 'add_top_or_bottom' in dict(config.items('locations')).keys():
		global add_top_or_bottom
		add_top_or_bottom = config.get('locations','add_top_or_bottom')

	# load selected database paths
	global selected
	global default_add_in
	global selected_path
	selected            = config.get('locations','selected').split(',')
	default_add_in      = config.get('locations','default_add_in')
	for d in selected:
		selected_path.append(os.path.expanduser(config.get('data',d)))

	# load autolink groups
	if 'autolink' in dict(config.items('locations')).keys():
		global autolink
		autolink_cfg        = config.get('locations','autolink')
		autolink            = [ autolink_cfg[i:i+ID_LENGTH] for i in range(0,len(autolink_cfg), ID_LENGTH+1)]

	# load list groups
	if 'list' in dict(config.items('locations')).keys():
		global list_of_groups
		list_cfg        = config.get('locations','list')
		list_of_groups  = [ list_cfg[i:i+ID_LENGTH] for i in range(0,len(list_cfg), ID_LENGTH+1)]

	# load status filters and status colors
	config_filter_names = dict(config.items('filters')).keys()
	config_color_names  = dict(config.items('colors')).keys()
	for n,nfilter in enumerate(TASK_STATUS):
		# strip to remove spaces in status strings
		# check that task_status filter is in config file, to avoid problems between config file versions
		if nfilter.strip().lower() in config_filter_names:
			status_filters[n]  = config.get('filters',nfilter.strip())
		if '%s_fgcolor'%nfilter.strip().lower() in config_color_names:
			status_fgColors[n] = tuple([int(i) for i in config.get('colors','%s_fgcolor'%nfilter.strip()).split(',')])
		if '%s_bgcolor'%nfilter.strip().lower() in config_color_names:
			status_bgColors[n] = tuple([int(i) for i in config.get('colors','%s_bgcolor'%nfilter.strip()).split(',')])

	# Set text editor
	global editor
	editor              = config.get('settings','editor')

	# set user name and email
	global user
	if 'username' in dict(config.items('settings')).keys():
		user  = config.get('settings','username')
	else:
		user  = getpass.getuser()
	if 'useremail' in dict(config.items('settings')).keys():
		global email
		email = config.get('settings','useremail')

	# init
	init()

